//
//  CoreDataManager.m
//  iOSStudy
//
//  Created by Evan Xie on 12/1/15.
//  Copyright © 2015 Family Wings. All rights reserved.
//


#import "CoreDataManager.h"
#import "MHWMigrationManager.h"


@interface CoreDataManager () <MHWMigrationManagerDelegate>
@property (nonatomic, strong) NSManagedObjectContext *backgroundManagedObjectContext;
@property (nonatomic, strong) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, strong) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@end

@implementation CoreDataManager

+ (instancetype)sharedManager
{
    static CoreDataManager *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [CoreDataManager new];
    });
    return instance;
}

- (instancetype)init
{
    if (self = [super init]) {

    }
    return self;
}

- (NSManagedObjectModel *)managedObjectModel
{
    if (!_managedObjectModel) {
        NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Phone" withExtension:@"momd"];
        _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    }
    return _managedObjectModel;
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (!_persistentStoreCoordinator) {
        
        NSError *error = nil;
        NSDictionary *options = @{ NSInferMappingModelAutomaticallyOption: @(YES) };
        _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
        
        if (![_persistentStoreCoordinator addPersistentStoreWithType:[self sourceStoreType]
                                                       configuration:nil
                                                                 URL:[self sourceStoreURL]
                                                             options:options
                                                               error:&error]) {
            
            NSLog(@"[CoreData Error]: %@", error);
            
            [[NSFileManager defaultManager] removeItemAtPath:[self sourceStoreURL].path error:nil];
            
            [[[UIAlertView alloc] initWithTitle:@"Ouch"
                                        message:error.localizedDescription
                                       delegate:nil
                              cancelButtonTitle:@"OK"
                              otherButtonTitles:nil] show];
        }
    }
    
    return _persistentStoreCoordinator;
}

- (NSManagedObjectContext *)backgroundManagedObjectContext
{
    if (!_backgroundManagedObjectContext) {
        _backgroundManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        _backgroundManagedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator;
        _backgroundManagedObjectContext.mergePolicy = [[NSMergePolicy alloc] initWithMergeType:NSMergeByPropertyObjectTrumpMergePolicyType];
        _backgroundManagedObjectContext.undoManager = nil;
    }
    
    return _backgroundManagedObjectContext;
}

- (void)insertTestData
{
    [self.backgroundManagedObjectContext performBlock:^{
        NSError *error = nil;
        NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([Phone class])];
        NSArray *phones = [self.backgroundManagedObjectContext executeFetchRequest:request error:&error];
        if (error) {
            NSLog(@"[CoreData Error] Insert TestData Interrupted.");
            return ;
        }
        
        if (phones.count == 0) {
            Phone *phone = (Phone *)[NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([Phone class])
                                                                       inManagedObjectContext:self.backgroundManagedObjectContext];
            phone.name = @"iPhone 6";
            
            Person *owner = (Person *)[NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass([Person class])
                                                                    inManagedObjectContext:self.backgroundManagedObjectContext];
            owner.firstName = @"Evan";
            owner.lastName = @"Xie";
            phone.owner = owner;
            
            NSLog(@"Inserting test data...");
            
            if (![self.backgroundManagedObjectContext save:&error]) {
                NSLog(@"[CoreData Error]: Insert test data failed.");
            }
        }
    }];
}

- (void)getAllPhonesWithResultBlock:(CoreDataOperationResultBlock)resultBlock
{
    if (resultBlock == NULL) {
        return;
    }
    
    [self.backgroundManagedObjectContext performBlock:^{
        NSError *error = nil;
        NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:NSStringFromClass([Phone class])];
        NSArray *phones = [self.backgroundManagedObjectContext executeFetchRequest:request error:&error];
        if (error) {
            NSLog(@"[CoreData Error] Fetch `Phone` failed.");
            resultBlock(nil, error);
        } else {
            resultBlock(phones, nil);
        }
    }];
}

#pragma mark - Data Migration

- (NSArray *)allObjectModelPaths
{
    //Find all of the mom and momd files in the Resources directory
    NSMutableArray *modelPaths = [NSMutableArray array];
    NSArray *momdArray = [[NSBundle mainBundle] pathsForResourcesOfType:@"momd"
                                                            inDirectory:nil];
    for (NSString *momdPath in momdArray) {
        NSString *resourceSubpath = [momdPath lastPathComponent];
        NSArray *array = [[NSBundle mainBundle] pathsForResourcesOfType:@"mom"
                                                            inDirectory:resourceSubpath];
        [modelPaths addObjectsFromArray:array];
    }
    NSArray *otherModels = [[NSBundle mainBundle] pathsForResourcesOfType:@"mom"
                                                              inDirectory:nil];
    [modelPaths addObjectsFromArray:otherModels];
    return modelPaths;
}

- (NSString *)objectModelName
{
    NSString *modelName = nil;
    NSArray *modelPaths = [self allObjectModelPaths];
    
    for (NSString *modelPath in modelPaths) {
        NSURL *modelURL = [NSURL fileURLWithPath:modelPath];
        NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
        if ([model isEqual:self.managedObjectModel]) {
            modelName = modelURL.lastPathComponent.stringByDeletingPathExtension;
            break;
        }
    }
    return modelName;
}

- (NSURL *)sourceStoreURL
{
    static NSURL *databaseURL = nil;
    
    if (!databaseURL) {
        NSString *savedDirectory = [AMDirectoryLibrary stringByAppendingPathComponent:@"us.familywings.iOSStudy"];
        [[NSFileManager defaultManager] createDirectoryAtPath:savedDirectory withIntermediateDirectories:YES attributes:nil error:nil];
        databaseURL = [NSURL fileURLWithPath:[savedDirectory stringByAppendingPathComponent:@"Phone.sqlite"]];
    }
    return databaseURL;
}

- (NSString *)sourceStoreType
{
    return NSSQLiteStoreType;
}

- (NSDictionary *)sourceMetadata:(NSError **)error
{
    return [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:[self sourceStoreType]
                                                                      URL:[self sourceStoreURL]
                                                                    error:error];
}

- (BOOL)isMigrationNeeded
{
    NSError *error = nil;
    
    // Check if we need to migrate
    NSDictionary *sourceMetadata = [self sourceMetadata:&error];
    BOOL isMigrationNeeded = NO;
    
    if (sourceMetadata != nil) {
        NSManagedObjectModel *destinationModel = self.managedObjectModel;
        
        // Migration is needed if destinationModel is NOT compatible
        isMigrationNeeded = ![destinationModel isConfiguration:nil compatibleWithStoreMetadata:sourceMetadata];
    }
    NSLog(@"isMigrationNeeded: %d", isMigrationNeeded);
    return isMigrationNeeded;
}

- (BOOL)migrate:(NSError *__autoreleasing *)error
{
    // Enable migrations to run even while user exits app
    __block UIBackgroundTaskIdentifier bgTask;
    bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        [[UIApplication sharedApplication] endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
    
    MHWMigrationManager *migrationManager = [MHWMigrationManager new];
    migrationManager.delegate = self;
    
    BOOL OK = [migrationManager progressivelyMigrateURL:[self sourceStoreURL]
                                                 ofType:[self sourceStoreType]
                                                toModel:[self managedObjectModel]
                                                  error:error];
    if (OK) {
        NSLog(@"migration complete");
    }
    
    // Mark it as invalid
    [[UIApplication sharedApplication] endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
    return OK;
}

#pragma mark - MHWMigrationManagerDelegate

- (void)migrationManager:(MHWMigrationManager *)migrationManager migrationProgress:(float)migrationProgress
{
    NSLog(@"migration progress: %f", migrationProgress);
}

- (NSArray *)migrationManager:(MHWMigrationManager *)migrationManager mappingModelsForSourceModel:(NSManagedObjectModel *)sourceModel
{
    NSMutableArray *mappingModels = [@[] mutableCopy];
    NSString *modelName = [self objectModelName];
    
    if ([modelName isEqual:@"Phone_v2"]) {
        // Migrating to Phone_v3
        NSArray *urls = [[NSBundle bundleForClass:[self class]] URLsForResourcesWithExtension:@"cdm" subdirectory:nil];
        for (NSURL *url in urls) {
            if ([url.lastPathComponent rangeOfString:@"PhoneV2ToV3"].length != 0) {
                NSMappingModel *mappingModel = [[NSMappingModel alloc] initWithContentsOfURL:url];
                [mappingModels addObject:mappingModel];
            }
        }
    }
    return mappingModels;
}

@end
